package com.faner4cloud.yun.common.util.progress;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.faner4cloud.yun.common.util.progress.AbstractTask.AbstractStepTask;
import com.faner4cloud.yun.common.util.progress.timer.ProcessTimer;
import com.faner4cloud.yun.common.util.progress.timer.TimerConfig;
import com.faner4cloud.yun.common.util.progress.timer.TimerFactory;
import org.springframework.util.StopWatch;


/**
 * 进度
 *
 */
public final class Progress {

	private static final String EMPTY = "";

	/**
	 * 单个进度工作的唯一标识
	 */
	private final String identity;

	private final StopWatch sw;

	/**
	 * 当前流程的进度状态
	 */
	private ProgressState state;

	/**
	 * 当前正在执行的任务
	 */
	private volatile AbstractTask doingTask;

	/**
	 * 已完成比率
	 */
	private volatile BigDecimal completedRate = BigDecimal.valueOf(0);

	/**
	 * 任务队列
	 */
	private final BlockingQueue<AbstractTask> tasks;

	/**
	 * 任务步骤，用于展示
	 */
	private final List<Step> steps;

	/**
	 * 进程定时器
	 */
	private final ProcessTimer timer;

	/**
	 * 任务结果
	 */
	private Object obj;

	private boolean preCheck = true;

	private boolean getPreCheck() {
		return this.preCheck;
	}

	/**
	 * 执行进度，含结果
	 */
	private Result result;

	private Consumer<Progress> thenAccept;

	private BiConsumer<Progress, ? super Throwable> exConsumer;

	private Exception error;

	Progress(String identity, List<AbstractTask> tasks, TimerConfig timerConfig, Consumer<Progress> thenAccept,
			 BiConsumer<Progress, ? super Throwable> exConsumer) {
		this.identity = identity;
		this.state = ProgressState.NEW;
		this.sw = new StopWatch(identity);
		this.tasks = new ArrayBlockingQueue<AbstractTask>(tasks.size(), false, tasks);
		this.steps = this.tasks.stream().map(t -> new Step(t.getName(), t.getRatePercent().doubleValue())).collect(Collectors.toList());
		this.timer = TimerFactory.createTimer(timerConfig);
		this.thenAccept = thenAccept;
		this.exConsumer = exConsumer;
	}

	/**
	 * 启动预判断
	 * </p>
	 * <b style="color:red">true才会开始 {@link Progress#run()}</b>
	 * </p>
	 * 通常这里可以用来检查分布式锁
	 * </p>
	 *
	 * @param predicate 检查条件
	 * @return {@link Progress}
	 */
	public Progress unstart(Predicate<Progress> predicate) {
		this.preCheck = predicate.test(this);
		return this;
	}

	/**
	 * 开启进度， 不使用线程池
	 *
	 * @author Jail.Hu
	 * @date 2019年4月1日 上午11:51:01
	 */
	public void run() {
		this.run(null);
	}

	/**
	 * 开启进度
	 *
	 * @param executorService 线程池
	 */
	public void run(ExecutorService executorService) {
		if (!this.getPreCheck()) {
			return;
		}
		if (this.state != ProgressState.NEW) {
			return;
		}
		if (executorService != null) {
			executorService.execute(new Job());
		} else {
			Thread tr = new Thread(new Job());
			tr.setName("progress-job-" + this.identity);
			tr.start();
		}
	}

	@SuppressWarnings({ "rawtypes" })
	Object doTask(AbstractStepTask task, Object o) {
		Object o1 = null;
		try {
			sw.start(task.getName());
			o1 = invokeTask(task, o);
		} catch (Exception e) {
			this.fail(e);
		} finally {
			sw.stop();
		}
		return o1;
	}

	/**
	 * @param task
	 * @param o
	 * @return
	 * @author Jail.Hu
	 * @date 2019年8月16日 下午5:12:33
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	Object invokeTask(AbstractStepTask task, Object o) {
		this.doingTask = task;
		Object o1 = task.process(o);
		this.completedRate = this.completedRate.add(task.getRatePercent());
		return o1;
	}

	/**
	 * 进程启动
	 */
	void start() {
		this.state = ProgressState.DOING;
		// 启动定时器
		this.timer.start(this);
	}

	/**
	 * 进程失败
	 *
	 * @param e
	 * @author Jail.Hu
	 * @date 2019年8月16日 下午5:10:10
	 */
	void fail(Exception e) {
		this.error = e;
		this.state = ProgressState.FAILED;
		this.timer.interrupt();
		if (this.exConsumer != null) {
			this.exConsumer.accept(this, e);
		} else {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 进程完成
	 *
	 * @param o 最后一条task的返回值
	 */
	void done(Object o) {
		this.state = ProgressState.DONE;
		this.doingTask = null;
		this.obj = o;
		this.timer.interrupt();
		if (this.thenAccept != null) {
			this.thenAccept.accept(this);
		}
	}

	boolean isFailed() {
		return this.state == ProgressState.FAILED;
	}

	/**
	 * 获得进度的完整步骤数据
	 */
	public List<Step> getSteps() {
		return this.steps;
	}

	/**
	 * 终止进程
	 */
	public void interrupt() {
		this.timer.interrupt();
	}

	/**
	 * 打印进度耗时
	 *
	 * @return String
	 * @author Jail.Hu
	 */
	public String printTimeSpent() {
		return this.sw.prettyPrint();
	}

	/**
	 * 获得进度执行总耗时
	 *
	 * @return long 总耗时
	 */
	public long getTotalTimeMillis() {
		return sw.getTotalTimeMillis();
	}

	/**
	 * 返回进程唯一标识
	 *
	 * @return long
	 */
	public String getIdentity() {
		return this.identity;
	}

	/**
	 * 当前执行的任务名
	 *
	 * @return String
	 */
	private String doingTaskName() {
		return Objects.isNull(this.doingTask) ? EMPTY : this.doingTask.getName();
	}

	/**
	 * 返回进程总进度
	 *
	 * @return double
	 */
	double getTotalProcess() {
		BigDecimal r = this.completedRate;
		if (Objects.nonNull(this.doingTask)) {
			r = r.add(this.doingTask.getCurrentPercent());
		}
		return r.setScale(2, RoundingMode.FLOOR).doubleValue();
	}

	/**
	 * 获得进度执行结果
	 *
	 * @return Result
	 * @author Jail.Hu
	 */
	public Result get() {
		if (Objects.isNull(this.result)) {
			this.result = new Result();
		}
		return this.result.fresh(this.identity, doingTaskName(), this.state, this.isFailed() ? this.error.getClass().getName() + "(" + this.error.getMessage() + ")" : this.obj, getTotalProcess());
	}

	class Job implements Runnable {
		@SuppressWarnings("rawtypes")
		@Override
		public void run() {
			Progress.this.start();
			Object o = null;
			AbstractTask task = null;
			while ((task = tasks.poll()) != null && !Progress.this.isFailed()) {
				if (task instanceof AbstractStepTask) {
					o = Progress.this.doTask((AbstractStepTask) task, o);
				}
			}
			if (!Progress.this.isFailed()) {
				Progress.this.done(o);
			}
		}
	}

	class Step {
		private final String name;
		private final double percent;

		public Step(String name, double percent) {
			super();
			this.name = name;
			this.percent = percent;
		}

		@Override
		public String toString() {
			return "Step [name=" + name + ", percent=" + percent + "]";
		}
	}
}
